#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
PyQt5上位机测试程序
地址发送：Mark校验位（第9位=1）
数据收发：Space校验位（第9位=0）
"""

import sys
import time
import threading
from PyQt5.QtWidgets import (QApplication, QMainWindow, QVBoxLayout, QHBoxLayout, 
                            QWidget, QPushButton, QTextEdit, QLineEdit, QLabel, 
                            QComboBox, QSpinBox, QGroupBox, QCheckBox, QMessageBox)
from PyQt5.QtCore import QTimer, pyqtSignal, QObject
from PyQt5.QtGui import QFont
import serial
import serial.tools.list_ports

class SerialWorker(QObject):
    """串口工作线程"""
    data_received = pyqtSignal(str)
    error_occurred = pyqtSignal(str)
    
    def __init__(self):
        super().__init__()
        self.serial_port = None
        self.running = False
        
    def open_port(self, port, baudrate):
        """打开串口"""
        try:
            self.serial_port = serial.Serial(
                port=port,
                baudrate=baudrate,
                bytesize=serial.EIGHTBITS,
                parity=serial.PARITY_SPACE,  # 默认Space校验
                stopbits=serial.STOPBITS_ONE,
                timeout=0.1
            )
            self.running = True
            return True
        except Exception as e:
            self.error_occurred.emit(f"打开串口失败: {str(e)}")
            return False
    
    def close_port(self):
        """关闭串口"""
        self.running = False
        if self.serial_port and self.serial_port.is_open:
            self.serial_port.close()
    
    def send_address(self, address):
        """发送地址（Mark校验位）"""
        if not self.serial_port or not self.serial_port.is_open:
            return False
        
        try:
            # 切换到Mark校验位模式
            self.serial_port.parity = serial.PARITY_MARK
            self.serial_port.write(bytes([address]))
            time.sleep(0.01)  # 短暂延时
            # 切换回Space校验位模式
            self.serial_port.parity = serial.PARITY_SPACE
            return True
        except Exception as e:
            self.error_occurred.emit(f"发送地址失败: {str(e)}")
            return False
    
    def send_data(self, data):
        """发送数据（Space校验位）"""
        if not self.serial_port or not self.serial_port.is_open:
            return False
        
        try:
            # 确保使用Space校验位
            self.serial_port.parity = serial.PARITY_SPACE
            if isinstance(data, str):
                self.serial_port.write(data.encode('utf-8'))
            else:
                self.serial_port.write(data)
            return True
        except Exception as e:
            self.error_occurred.emit(f"发送数据失败: {str(e)}")
            return False
    
    def read_data(self):
        """读取数据"""
        if not self.serial_port or not self.serial_port.is_open:
            return
        
        try:
            if self.serial_port.in_waiting > 0:
                data = self.serial_port.read(self.serial_port.in_waiting)
                hex_str = ' '.join([f'{b:02X}' for b in data])
                ascii_str = ''.join([chr(b) if 32 <= b <= 126 else f'[{b:02X}]' for b in data])
                timestamp = time.strftime("%H:%M:%S")
                self.data_received.emit(f"[{timestamp}] 接收: {hex_str} | {ascii_str}")
        except Exception as e:
            self.error_occurred.emit(f"读取数据失败: {str(e)}")

class SerialTestGUI(QMainWindow):
    def __init__(self):
        super().__init__()
        self.serial_worker = SerialWorker()
        self.read_timer = QTimer()
        self.read_timer.timeout.connect(self.serial_worker.read_data)
        
        # 连接信号
        self.serial_worker.data_received.connect(self.on_data_received)
        self.serial_worker.error_occurred.connect(self.on_error_occurred)
        
        self.init_ui()
        self.refresh_ports()
        
    def init_ui(self):
        """初始化用户界面"""
        self.setWindowTitle("串口测试工具 - Mark/Space校验位")
        self.setGeometry(100, 100, 800, 600)
        
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        layout = QVBoxLayout(central_widget)
        
        # 串口配置区域
        config_group = QGroupBox("串口配置")
        config_layout = QHBoxLayout(config_group)
        
        config_layout.addWidget(QLabel("端口:"))
        self.port_combo = QComboBox()
        config_layout.addWidget(self.port_combo)
        
        config_layout.addWidget(QLabel("波特率:"))
        self.baudrate_combo = QComboBox()
        self.baudrate_combo.addItems(['9600', '19200', '38400', '57600', '115200'])
        self.baudrate_combo.setCurrentText('9600')
        config_layout.addWidget(self.baudrate_combo)
        
        self.refresh_btn = QPushButton("刷新端口")
        self.refresh_btn.clicked.connect(self.refresh_ports)
        config_layout.addWidget(self.refresh_btn)
        
        self.connect_btn = QPushButton("连接")
        self.connect_btn.clicked.connect(self.toggle_connection)
        config_layout.addWidget(self.connect_btn)
        
        layout.addWidget(config_group)
        
        # 地址发送区域
        addr_group = QGroupBox("地址发送 (Mark校验位)")
        addr_layout = QHBoxLayout(addr_group)
        
        addr_layout.addWidget(QLabel("设备地址:"))
        self.addr_spinbox = QSpinBox()
        self.addr_spinbox.setRange(0, 255)
        self.addr_spinbox.setValue(0xE1)
        self.addr_spinbox.setDisplayIntegerBase(16)
        self.addr_spinbox.setPrefix("0x")
        addr_layout.addWidget(self.addr_spinbox)
        
        self.send_addr_btn = QPushButton("发送地址")
        self.send_addr_btn.clicked.connect(self.send_address)
        self.send_addr_btn.setEnabled(False)
        addr_layout.addWidget(self.send_addr_btn)
        
        layout.addWidget(addr_group)
        
        # 数据发送区域
        data_group = QGroupBox("数据发送 (Space校验位)")
        data_layout = QVBoxLayout(data_group)
        
        send_layout = QHBoxLayout()
        self.data_input = QLineEdit()
        self.data_input.setPlaceholderText("输入要发送的数据...")
        send_layout.addWidget(self.data_input)
        
        self.send_data_btn = QPushButton("发送数据")
        self.send_data_btn.clicked.connect(self.send_data)
        self.send_data_btn.setEnabled(False)
        send_layout.addWidget(self.send_data_btn)
        
        data_layout.addLayout(send_layout)
        
        # 十六进制发送
        hex_layout = QHBoxLayout()
        self.hex_input = QLineEdit()
        self.hex_input.setPlaceholderText("输入十六进制数据 (如: 01 02 03 FF)")
        hex_layout.addWidget(self.hex_input)
        
        self.send_hex_btn = QPushButton("发送HEX")
        self.send_hex_btn.clicked.connect(self.send_hex_data)
        self.send_hex_btn.setEnabled(False)
        hex_layout.addWidget(self.send_hex_btn)
        
        data_layout.addLayout(hex_layout)
        layout.addWidget(data_group)
        
        # 接收显示区域
        recv_group = QGroupBox("数据接收")
        recv_layout = QVBoxLayout(recv_group)
        
        self.recv_text = QTextEdit()
        self.recv_text.setReadOnly(True)
        self.recv_text.setFont(QFont("Consolas", 9))
        recv_layout.addWidget(self.recv_text)
        
        clear_layout = QHBoxLayout()
        self.clear_btn = QPushButton("清空接收")
        self.clear_btn.clicked.connect(self.clear_received)
        clear_layout.addWidget(self.clear_btn)
        
        self.auto_scroll_cb = QCheckBox("自动滚动")
        self.auto_scroll_cb.setChecked(True)
        clear_layout.addWidget(self.auto_scroll_cb)
        
        clear_layout.addStretch()
        recv_layout.addLayout(clear_layout)
        
        layout.addWidget(recv_group)
        
        # 状态栏
        self.statusBar().showMessage("就绪")
        
    def refresh_ports(self):
        """刷新可用串口"""
        self.port_combo.clear()
        ports = serial.tools.list_ports.comports()
        for port in ports:
            self.port_combo.addItem(f"{port.device} - {port.description}")
        
        if not ports:
            self.port_combo.addItem("未找到可用串口")
            
    def toggle_connection(self):
        """切换连接状态"""
        if self.connect_btn.text() == "连接":
            self.connect_serial()
        else:
            self.disconnect_serial()
            
    def connect_serial(self):
        """连接串口"""
        if self.port_combo.count() == 0 or self.port_combo.currentText() == "未找到可用串口":
            QMessageBox.warning(self, "警告", "请选择有效的串口")
            return
            
        port = self.port_combo.currentText().split(' - ')[0]
        baudrate = int(self.baudrate_combo.currentText())
        
        if self.serial_worker.open_port(port, baudrate):
            self.connect_btn.setText("断开")
            self.send_addr_btn.setEnabled(True)
            self.send_data_btn.setEnabled(True)
            self.send_hex_btn.setEnabled(True)
            self.read_timer.start(50)  # 50ms读取一次
            self.statusBar().showMessage(f"已连接到 {port} @ {baudrate}")
        else:
            QMessageBox.critical(self, "错误", "连接串口失败")
            
    def disconnect_serial(self):
        """断开串口"""
        self.read_timer.stop()
        self.serial_worker.close_port()
        self.connect_btn.setText("连接")
        self.send_addr_btn.setEnabled(False)
        self.send_data_btn.setEnabled(False)
        self.send_hex_btn.setEnabled(False)
        self.statusBar().showMessage("已断开连接")
        
    def send_address(self):
        """发送设备地址"""
        address = self.addr_spinbox.value()
        if self.serial_worker.send_address(address):
            timestamp = time.strftime("%H:%M:%S")
            self.recv_text.append(f"[{timestamp}] 发送地址: 0x{address:02X} (Mark校验位)")
            if self.auto_scroll_cb.isChecked():
                self.recv_text.moveCursor(self.recv_text.textCursor().End)
                
    def send_data(self):
        """发送文本数据"""
        data = self.data_input.text()
        if not data:
            return
            
        if self.serial_worker.send_data(data):
            timestamp = time.strftime("%H:%M:%S")
            self.recv_text.append(f"[{timestamp}] 发送数据: {data} (Space校验位)")
            self.data_input.clear()
            if self.auto_scroll_cb.isChecked():
                self.recv_text.moveCursor(self.recv_text.textCursor().End)
                
    def send_hex_data(self):
        """发送十六进制数据"""
        hex_str = self.hex_input.text().strip()
        if not hex_str:
            return
            
        try:
            # 解析十六进制字符串
            hex_bytes = bytes.fromhex(hex_str.replace(' ', ''))
            if self.serial_worker.send_data(hex_bytes):
                timestamp = time.strftime("%H:%M:%S")
                formatted_hex = ' '.join([f'{b:02X}' for b in hex_bytes])
                self.recv_text.append(f"[{timestamp}] 发送HEX: {formatted_hex} (Space校验位)")
                self.hex_input.clear()
                if self.auto_scroll_cb.isChecked():
                    self.recv_text.moveCursor(self.recv_text.textCursor().End)
        except ValueError:
            QMessageBox.warning(self, "警告", "无效的十六进制格式")
            
    def on_data_received(self, data):
        """处理接收到的数据"""
        self.recv_text.append(data)
        if self.auto_scroll_cb.isChecked():
            self.recv_text.moveCursor(self.recv_text.textCursor().End)
            
    def on_error_occurred(self, error):
        """处理错误"""
        self.recv_text.append(f"错误: {error}")
        if self.auto_scroll_cb.isChecked():
            self.recv_text.moveCursor(self.recv_text.textCursor().End)
            
    def clear_received(self):
        """清空接收区域"""
        self.recv_text.clear()
        
    def closeEvent(self, event):
        """关闭事件"""
        self.disconnect_serial()
        event.accept()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = SerialTestGUI()
    window.show()
    sys.exit(app.exec_())
